/** * HTMLComponentFactory - Factory which creates XMLComponents from an HTML Doc. * * Copyright (c) 2000 * Marty Phelan, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package com.taursys.html; import java.util.*; import java.text.*; import org.w3c.dom.*; import com.taursys.xml.*; import com.taursys.html.*; import com.taursys.model.*; import com.taursys.debug.*; import com.taursys.dom.*; /** * HTMLComponentFactory is used to automate the creation of Components based on * the HTML Document and its Elements. It determines the Component type based * on its element tag "type" attribute and "id" attribute. This class * initializes the tagTable with suggested components for HTML tags in its * constructor by calling the <code>initTagTable</code> method. * <p> * This class provides a Singleton via the <code>getInstance</code> method. This * is recommended over constructing a new instance. * <p> * This class contains two primary methods: * <ul> * <li><code>getSuggestedComponents</code> - for use by design tools.</li> * <li><code>createComponents</code> - to automatically create and bind * <code>Components</code> at runtime.</li> * </ul> */ public class HTMLComponentFactory extends ComponentFactory { private static HTMLComponentFactory factory; /** * Default constructor for HTMLComponentFactory. * @see #getInstance */ public HTMLComponentFactory() { super(); } /** * Get the singleton instance of the HTMLComponentFactory. */ public static HTMLComponentFactory getInstance() { if (factory == null) { factory = new HTMLComponentFactory(); } return factory; } // *********************************************************************** // * PROTECTED METHODS // *********************************************************************** /** * Initialize the factory's tagTable with suggested components for HTML * documents. During the automated Component creation, only the first * suggestion is used. The other suggestions are intended for use by design * tools. The following are the suggestions created by this class: * <table> * <tr> * <td>Element Tag</td> * <td>Type Attribute</td> * <td>Suggested Component(s)</td> * </tr> * <tr> * <td>a</td> * <td>n/a</td> * <td>HTMLAnchorURL</td> * </tr> * <tr> * <td>select</td> * <td>na</td> * <td>HTMLSelect</td> * </tr> * <tr> * <td>span</td> * <td>n/a</td> * <td>TextField</td> * </tr> * <tr> * <td>td</td> * <td>n/a</td> * <td>TextField</td> * </tr> * <tr> * <td>textarea</td> * <td>n/a</td> * <td>HTMLInputText</td> * </tr> * <tr> * <td>input</td> * <td>hidden</td> * <td>HTMLInputText</td> * </tr> * <tr> * <td>input</td> * <td>password</td> * <td>HTMLInputText</td> * </tr> * <tr> * <td>input</td> * <td>submit</td> * <td>Button, Trigger</td> * </tr> * <tr> * <td>input</td> * <td>text</td> * <td>HTMLInputText</td> * </tr> * <tr> * <td>input</td> * <td>checkbox</td> * <td>HTMLCheckBox</td> * </tr> * </table> * <p> */ protected void initTagTable() { Vector suggestions = new Vector(); suggestions.add(HTMLAnchorURL.class.getName()); tagTable.put("a", suggestions); suggestions = new Vector(); suggestions.add(HTMLSelect.class.getName()); tagTable.put("select", suggestions); suggestions = new Vector(); suggestions.add(TextField.class.getName()); tagTable.put("span", suggestions); suggestions = new Vector(); suggestions.add(TextField.class.getName()); tagTable.put("td", suggestions); suggestions = new Vector(); suggestions.add(HTMLTextArea.class.getName()); tagTable.put("textarea", suggestions); suggestions = new Vector(); suggestions.add(HTMLInputText.class.getName()); tagTable.put("input-hidden", suggestions); suggestions = new Vector(); suggestions.add(HTMLInputText.class.getName()); tagTable.put("input-password", suggestions); suggestions = new Vector(); suggestions.add(Button.class.getName()); suggestions.add(Trigger.class.getName()); tagTable.put("input-submit", suggestions); suggestions = new Vector(); suggestions.add(HTMLInputText.class.getName()); tagTable.put("input-text", suggestions); suggestions = new Vector(); suggestions.add(HTMLCheckBox.class.getName()); tagTable.put("input-checkbox", suggestions); } /** * Returns a Vector of suggested Component class names for given Element. * This method will choose the appropriate Components based on the type of * Element given. The default Component type will be the first in the list. * <p> * If the component is an input component, then the TYPE attribute is also * used in selecting the right component. * <p> * If the Element has an ID, then the com.taursys.xml.DocumentElement Component * will be added to the end of the suggestion list. * <p> * If the id contains the TEMPLATE_NODE keyword, then a Template will be added * to the top of the suggestion list. * <p> * If there are no suggested types of Component for the given Element, then * an empty Vector will be returned. * <p> * Subclasses should override this method if more than the Element tag name * is needed to determine the suggested components. * @param element to return default Component type for * @return a Vector containing any suggested Component class names */ public Vector getSuggestedComponents(Element element) { String tagName = element.getTagName(); if (tagName.equals("input")) tagName += "-" + element.getAttribute("type"); String id = element.getAttribute("id"); return getSuggestedComponents(tagName, id, element); } /** * <p>Create a component for given element and set its properties. * Only bound components with id's following a strict id naming convention * will be created. * </p> * <p>The id must begin with a ValueHolder's alias. It must then be followed * by a double-underscore ("__"). Next the propertyName must appear or the * keyword "TEMPLATE_NODE". An optional suffix can be added to ensure * unique id's (as required by spec). The optional suffix must be separated * from the property name by a double-underscore("__"). The following are * examples of valid id format:</p> * <ul> * <li>Person__lastName</li> * <li>Person__lastName__2</li> * <li>Invoices__TEMPLATE_NODE</li> * <li>Invoices__TEMPLATE_NODE__2</li> * </ul> * <p>The alias of the id (first part), must match an alias of a ValueHolder * in the given array of ValueHolders, otherwise no Component will be * created. The ValueHolder with a matching alias will be set as the new * Component's valueHolder. * </p> * <p>If the new Component is an AbstractField subclass, then its * propertyName will be set to the propertyName of the id (second part). If * the given Element has a "name" attribute, the Component's parameter will * be set to the value of the "name" attribute. * <p>If the new Component is a Template (or subclass), then only its * collectionValueHolder property will be set. The associated ValueHolder * for this Component must be a CollectionValueHolder, otherwise no * Component will be created. * </p> * <p>This method will also attempt to setup the formatting properties * for the new Component. This only applies to AbstractField subclasses. * The format is extracted from the document within the element's "value" * attribute, "href" attribute, or text node. The format must be specified * as TYPE:pattern, where TYPE is one of: DATE NUMBER or MSG. The pattern * should be a valid pattern for the format type. The following are examples * of use: * <ul> * <li><span id="Person__birthdate">DATE:MM/dd/yyyy</span></li> * <li><input type="text" id="InvoiceItem__unitPrice" value="NUMBER:###,##0.00" /></li> * <li><a id="Person__personID" href="MSG:/PersonProfile.mxform?personID={0}">Link to Person</a></li> * </ul> * </p> * @param id of Element to create component for * @param element to create component for * @param holders the array of ValueHolders for binding * @return new Component with properties set or null */ protected Component createComponentForElement(String id, Element element, ValueHolder[] holders) { if (id == null) throw new IllegalArgumentException( "Null id passed to createComponentForElement"); // Extract holder alias int pos = id.indexOf(ID_DELIMITER); if (pos < 1) return null; String alias = id.substring(0, pos); // Extract property name (remove suffix) pos += 2; if (id.length() <= pos) return null; String propertyName = id.substring(pos); pos = propertyName.indexOf(ID_DELIMITER); if (pos != -1) propertyName = propertyName.substring(0, pos); // Find holder for alias ValueHolder holder = null; for (int i = 0; i < holders.length; i++) { holder = holders[i]; if (alias.equals(holder.getAlias())) break; } if (holder == null) return null; // Create appropriate component Vector suggestions = getSuggestedComponents(element); if (suggestions.size() == 0) return null; Component component = null; try { component = (Component) Class.forName((String)suggestions.get(0)).newInstance(); } catch (Exception ex) { Debug.error("Error during create component: " + ex.getMessage(), ex); return null; } // Set properties as appropriate: // id, valueHolder, propertyName, parameter, format, formatPattern) if (component instanceof AbstractField) { ((AbstractField)component).setId(id); ((AbstractField)component).setValueHolder(holder); ((AbstractField)component).setPropertyName(propertyName); String parameter = element.getAttribute("name"); if (parameter != null && parameter.length() == 0) parameter = null; ((AbstractField)component).setParameter(parameter); setupFormat((AbstractField)component, element); return component; } else if (component instanceof Template) { ((Template)component).setId(id); if (holder instanceof CollectionValueHolder) { ((Template)component).setCollectionValueHolder((CollectionValueHolder)holder); return component; } else { return null; } } else { return null; } } private void setupFormat(AbstractField field, Element element) { // Try to get format from value attribute or text node String formatInfo = element.getAttribute("value"); if (formatInfo == null || formatInfo.length() == 0) formatInfo = element.getAttribute("href"); if (formatInfo == null || formatInfo.length() == 0) formatInfo = DOM_1_20000929_DocumentAdapter.getElementText(element); // Determine what kind of format this is if (formatInfo != null && formatInfo.length() > 0) { if (formatInfo.startsWith("DATE:")) { field.setFormat(new java.text.SimpleDateFormat()); field.setFormatPattern(formatInfo.substring(5)); } else if (formatInfo.startsWith("NUMBER:")) { field.setFormat(new DecimalFormat()); field.setFormatPattern(formatInfo.substring(7)); } else if (formatInfo.startsWith("MSG:")) { field.setFormat(new MessageFormat("")); field.setFormatPattern(formatInfo.substring(4)); } } } }